﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using KSS.Generator.Configuration;
using KSS.Generator.Schema;
using KSS.Patterns.Serializer;
using System.Reflection;

namespace KSS.Generator
{
    public class MainGenerator
    {
        #region [ Properties ]

        private string _configurationFile;
        public string ConfigurationFile
        {
            get
            {
                return _configurationFile;
            }
            set
            {
                _configurationFile = value;
            }
        }

        private GeneratorContext _context;
        public GeneratorContext Context
        {
            get
            {
                if (_context == null)
                {
                    _context = new GeneratorContext();
                }

                return _context;
            }
            set { _context = value; }
        }

        public Configuration.GeneratorSetting Setting
        {
            get
            {
                return Context.Setting;
            }
        }

        private List<ProjectGenerator> _generators;
        public List<ProjectGenerator> Generators
        {
            get
            {
                if (_generators == null)
                {
                    _generators = new List<ProjectGenerator>();
                }

                return _generators;
            }
        }

        #endregion

        public MainGenerator()
        {
        }
        public MainGenerator(string configFile)
        {
            Open(configFile);
        }

        /// <summary>
        /// Open a config file and load to Setting
        /// </summary>
        /// <param name="configFile"></param>
        public void Open(string configFile)
        {
            _configurationFile = configFile;
            GetSettingFromFile();
        }

        #region [ Generating ]

        /// <summary>
        /// Generate whole solution
        /// </summary>
        public void Generate()
        {
            if (Setting == null)
            {
                if (string.IsNullOrEmpty(_configurationFile) == false)
                {
                    GetSettingFromFile();
                }
                else
                {
                    return;
                }
            }

            // Initialize template directory list
            string[] projectFolders = Directory.GetDirectories(Setting.TemplateFolderPath);
            for (int i = 0; i < projectFolders.Length; i++)
            {
                Context.TemplateDirectories.Add(new DirectoryInfo(projectFolders[i]));
            }

            // Get database schema from dbml file
            Context.DatabaseSchema = GenericSerializer<Database>.DeserializeFromFile(Setting.DatabaseFilePath);

            StructureGenerator structureGenerator = new StructureGenerator();
            structureGenerator.Context = Context;
            structureGenerator.Generate();

            string generatorFullName = string.Empty;
            Assembly generatorAssembly = null;
            ProjectGenerator generator = null;
            System.Type generatorType = null;
            foreach (ProjectSetting projectSetting in Setting.Projects)
            {
                if (string.IsNullOrEmpty(projectSetting.GeneratorAssembly))
                {
                    generatorAssembly = Assembly.GetExecutingAssembly();
                }
                else
                {
                    generatorAssembly = Assembly.Load(projectSetting.GeneratorAssembly);
                }

                if (generatorAssembly == null)
                {
                    throw new Exception("The generator assembly is not exist.");
                }

                generatorFullName = projectSetting.GeneratorName;
                generatorType = generatorAssembly.GetType(generatorFullName);

                if (generatorType != null)
                {
                    generator = Activator.CreateInstance(generatorType) as ProjectGenerator;
                }

                //generator = generatorAssembly.CreateInstance(generatorFullName) as ProjectGenerator;

                if (generator != null)
                {
                    generator.Context = Context;
                    generator.Generate();
                }
                else
                {
                    throw new Exception("Can not create instance of generator type: " + projectSetting.GeneratorName);
                }
            }
        }
        #endregion

        #region [ Settings ]

        /// <summary>
        /// Save Setting field to file
        /// </summary>
        /// <param name="settingFile"></param>
        public void SaveSetting(string settingFile)
        {
            GenericSerializer<GeneratorSetting>.SerializeToFile(Context.Setting, settingFile);
        }

        /// <summary>
        /// Load setting information from config file to Setting field
        /// </summary>
        private void GetSettingFromFile()
        {
            try
            {
                Context.Setting = GenericSerializer<Configuration.GeneratorSetting>.DeserializeFromFile(_configurationFile);
            }
            catch (System.Exception e)
            {
                throw new Exception("The config file is not valid. " + e.Message);
            }
        }
        #endregion

        #region [ Schema generating ]
        /// <summary>
        /// Generate schema of config file from template folders
        /// </summary>
        /// <param name="templateFolder"></param>
        /// <param name="schemaFilePath"></param>
        public static void GenerateSchemaFromTemplateFolders(string templateFolder, string schemaFilePath)
        {
            DirectoryInfo rootTemplateFolder = new DirectoryInfo(templateFolder);

            GeneratorSetting generatorConfiguration = new GeneratorSetting();
            ProjectSetting projectConfiguration = null;
            List<PropertySetting> propertySettings = null;
            foreach (DirectoryInfo childTemplateFolder in rootTemplateFolder.GetDirectories())
            {
                projectConfiguration = new ProjectSetting();
                projectConfiguration.Name = childTemplateFolder.Name;
                projectConfiguration.TemplateFolderName = childTemplateFolder.Name;
                projectConfiguration.ProjectFolderName = childTemplateFolder.Name;
                propertySettings = GetPropertySettings(childTemplateFolder);

                projectConfiguration.Properties.AddRange(ConfigurationHelper.RemoveDuplicateSections(propertySettings));
                generatorConfiguration.Projects.Add(projectConfiguration);
            }

            GenericSerializer<GeneratorSetting>.SerializeToFile(generatorConfiguration, schemaFilePath);
        }

        private static List<PropertySetting> GetPropertySettings(DirectoryInfo templateFolder)
        {
            List<PropertySetting> settingColl = new List<PropertySetting>();
            string templateFileContent;
            PropertySetting setting = null;

            foreach (FileInfo templateFile in templateFolder.GetFiles(Template.TEMPLATE_FILE_EXTENSION))
            {
                templateFileContent = GeneratorHelper.ReadFromFile(templateFile.FullName);

                List<string> localProperties = ParseLocalProperty(templateFileContent);
                List<string> globalProperties = ParseGlobalProperty(templateFileContent);
                List<string> customProperties = ParseCustomProperty(templateFileContent);

                foreach (string property in localProperties)
                {
                    setting = new PropertySetting(property, PropertyType.Local);
                    settingColl.Add(setting);
                }
                foreach (string property in globalProperties)
                {
                    setting = new PropertySetting(property, PropertyType.Global);
                    settingColl.Add(setting);
                }
                foreach (string property in customProperties)
                {
                    setting = new PropertySetting(property, PropertyType.Custom);
                    settingColl.Add(setting);
                }
            }

            foreach (DirectoryInfo childFolder in templateFolder.GetDirectories())
            {
                settingColl.AddRange(GetPropertySettings(childFolder));
            }

            return settingColl;
        }
        private static List<string> ParseLocalProperty(string templateContent)
        {
            List<string> globalProperties = ParseProperty(templateContent, Template.GLOBAL_PROPERTY_CHARACTER);
            List<string> localProperties = ParseProperty(templateContent, Template.LOCAL_PROPERTY_CHARACTER);

            localProperties.RemoveAll(
                delegate(string p)
                {
                    return (globalProperties.Contains(p));
                });

            return localProperties;
        }
        private static List<string> ParseGlobalProperty(string templateContent)
        {
            List<string> customProperties = ParseCustomProperty(templateContent);
            List<string> globalProperties = ParseProperty(templateContent, Template.GLOBAL_PROPERTY_CHARACTER);
            globalProperties.RemoveAll(
                delegate(string p)
                {
                    return (customProperties.Contains(p));
                });

            return globalProperties;
        }
        private static List<string> ParseCustomProperty(string templateContent)
        {
            return ParseProperty(templateContent, Template.CUSTOM_PROPERTY_CHARACTER);
        }
        private static List<string> ParseProperty(string templateContent, string specialCharacters)
        {
            List<string> propertyList = new List<string>();
            templateContent = templateContent.Trim();

            string[] param = new string[] { specialCharacters };
            string[] properties = templateContent.Split(param, StringSplitOptions.RemoveEmptyEntries);

            int step = (templateContent.Substring(0, specialCharacters.Length) == specialCharacters) ? 0 : 1;
            for (int i = 0; i < properties.Length; i += 2)
            {
                if (i + step < properties.Length)
                {
                    propertyList.Add(properties[i + step].Replace(Template.LOCAL_PROPERTY_CHARACTER, string.Empty));
                }
            }

            return propertyList;
        }

        #endregion
    }
}